package cn.jbolt.ai.core.chain.node.action;

import cn.hutool.core.util.StrUtil;
import cn.jbolt.ai.core.chain.config.AppConfigConstant;
import cn.jbolt.ai.core.chain.config.LlmConfig;
import cn.jbolt.ai.core.chain.config.KlbChatNodeConfig;
import cn.jbolt.ai.app.entity.AiAppMessage;
import cn.jbolt.ai.app.service.AiAppMessageService;
import cn.jbolt.ai.app.service.AiAppService;
import cn.jbolt.ai.core.chain.ChainContext;
import cn.jbolt.ai.core.chain.node.provider.BaseNode;
import cn.jbolt.ai.core.chain.node.provider.EventNodeProvider;
import cn.jbolt.ai.klb.FileType;
import cn.jbolt.ai.klb.entity.AiDoc;
import cn.jbolt.util.JSONUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.jboltai.capability.model.JBoltAI;
import com.jboltai.event.Event;
import com.jboltai.event.ai.aiknowledge.AIKnowledgeEvent;
import com.jboltai.event.signal.FailEvent;
import com.jboltai.event.signal.SuccessEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;


/**
 * 知识库回答的节点
 */
@Component
public class KlbChatNodeProvider extends BaseNode implements EventNodeProvider<Event> {

    @Autowired
    private AiAppService aiAppService;
    @Autowired
    private AiAppMessageService aiAppMessageService;
    private static final Logger logger = LoggerFactory.getLogger(KlbChatNodeProvider.class);

    public static final String CHAT_WITH_KNOWLEDGE_ONLY = "你是一个专业的问答助手。请针对用户提问提供精确、有帮助的回答。\n" +
            "\n" +
            "## 背景知识\n" +
            "{background}\n" +
            "\n" +
            "## 用户问题\n" +
            "{question}\n" +
            "\n" +
            "## 回答原则与格式要求\n" +
            "\n" +
            "### 核心规则\n" +
            "- **仅使用背景知识**：只输出背景知识中存在的内容，不存在的内容禁止输出\n" +
            "- **保持原样输出**：所有URL、路径和链接必须完全保持原样，不做任何修改\n" +
            "- **结构清晰**：使用markdown格式，条理分明，重点内容加粗\n" +
            "- **忠于原文**：不添加背景知识中不存在的解释，直接引用原文\n" +
            "\n" +
            "### 特殊情况处理\n" +
            "1. **问题无关**：当用户问题与背景知识完全无关时，必须直接输出（不允许添加任何其他解释）：{nomatch}\n" +
            "2. **社交用语**：对于问候语、语气词等，使用简短自然对话回复，不引用背景知识\n" +
            "\n" +
            "### 媒体与链接处理\n" +
            "- 完整保留所有图片、视频、音频、附件URL及相对路径\n" +
            "- 使用正确markdown语法渲染所有媒体和链接\n" +
            "- 不添加背景知识中不存在的任何URL\n" +
            "\n" +
            "### 回答格式\n" +
            "- 不以\"根据背景知识\"等词语开头\n" +
            "- 不使用```markdown```格式包裹整个回答\n" +
            "- 不展示思考过程，只呈现最终答案\n" +
            "\n" +
            "请直接提供精准回答，无需解释如何应用以上规则。";

    /**
     * 根据知识库回答的模版
     */
    public static final String CHAT_WITH_KNOWLEDGE = "你是一个专业的问答助手。请针对用户提问提供精确、有帮助的回答。\n" +
            "\n" +
            "## 知识来源优先级\n" +
            "1. 首要回答来源：提供的背景知识\n" +
            "2. 次要回答来源（仅当背景知识不包含相关信息时）：你的通用知识\n" +
            "\n" +
            "## 背景知识\n" +
            "{background}\n" +
            "\n" +
            "## 用户问题\n" +
            "{question}\n" +
            "\n" +
            "## 回答规则\n" +
            "1. **内容准确性**\n" +
            "   - 严格使用背景知识中已有的信息\n" +
            "   - 不添加、不臆测背景知识中不存在的内容\n" +
            "   - 如背景知识无法回答且非社交用语，才使用通用知识\n" +
            "\n" +
            "2. **链接与媒体处理**\n" +
            "   - 保留所有原始URL和相对路径（图片、视频、音频、附件等）\n" +
            "   - 使用markdown格式正确渲染所有链接和媒体\n" +
            "   - 不修改任何URL地址，完整保留原文格式\n" +
            "   \n" +
            "3. **回答结构与格式**\n" +
            "   - 使用清晰、结构化的markdown格式\n" +
            "   - 重要内容使用**加粗**标记\n" +
            "   - 分段组织信息，提高可读性\n" +
            "   - 不在回答外层添加```markdown```包裹\n" +
            "\n" +
            "4. **特殊情况处理**\n" +
            "   - 问候语、客套话：直接以自然对话方式简短回复，不引用背景知识\n" +
            "   - 背景知识缺失但问题合理：明确使用通用知识回答\n" +
            "\n" +
            "5. **禁止事项**\n" +
            "   - 不使用\"根据背景知识\"等引导语开头\n" +
            "   - 不输出思考过程\n" +
            "   - 不添加背景知识中不存在的解释性内容\n" +
            "   - 不修改任何URL或路径\n" +
            "\n" +
            "请直接提供最终答案，无需解释你如何应用这些规则。";

    public static void main(String[] args) {
        System.out.println(CHAT_WITH_KNOWLEDGE_ONLY);
    }


    public Event provide(JsonNode nodeConfig, ChainContext context) {
        try {
            LlmConfig llmConfig = LlmConfig.from(nodeConfig.get(AppConfigConstant.CONFIG_TYPE_LLM));
            KlbChatNodeConfig klbChatConfig = KlbChatNodeConfig.from(nodeConfig.get(AppConfigConstant.CONFIG_TYPE_KLB_CHAT));
            if (klbChatConfig == null) {
                logger.error("APP [{}:{}] 节点未配置", context.getAiApp().getSn(), context.getAiApp().getName());
                throw new RuntimeException("节点未配置");
            }
            List<AiDoc> docs = new ArrayList<>();
            try {

                List<AiDoc> res = context.getAttr(klbChatConfig.getKlbResultField());
                if (res != null) {
                    docs.addAll(res);
                }
            } catch (Exception e) {
                logger.error("ChatNodeHandler process 异常,异常原因", e);
                context.sendErrorMsg("知识库查询结果配置不正确");
                return new FailEvent("知识库查询结果配置不正确");
            }

//            if ((docs == null || docs.size() == 0) && klbChatConfig.isOnlyKlb()) {
//                //仅知识库模式，还没查到相关资料
//                if (llmConfig.isOutputToResponse()) {
//                    context.sendResponseMsg(klbChatConfig.getNoMatchReply(), "markdown");
//                }
//                if (llmConfig.isPersistDb()) {
//                    //把答案给存储
//                    context.getResponseMsg().appendContent(klbChatConfig.getNoMatchReply());
//                    return new SuccessEvent(null);
//                }
//            }


            //生成背景知识
            List<String> background = generateBackgroundText(docs);
            AIKnowledgeEvent event = JBoltAI.chatWithKnowledge(null, background, klbChatConfig.getNoMatchReply());
            event.setKnowledgeOnly(klbChatConfig.isOnlyKlb());
            event.setPromptTpl(klbChatConfig.isOnlyKlb() ? CHAT_WITH_KNOWLEDGE_ONLY : CHAT_WITH_KNOWLEDGE);
            //根据配置初始化event
            super.configLlm(llmConfig, context, event);
            event.onSuccess((e, res) -> {
                if (llmConfig.isOutputToResponse()) {
                    //如果配置项要求 内容返回到响应里，那就输出，默认是true的
                    //构建相应信息
                    context.sendResponseMsg(res.getContent(), llmConfig.getFormat());
                }
            });
            event.onFail((e, error) -> {
                logger.error("ChatWithKlbn节点执行发生异常,异常原因:{}", error.getFullMsg());
            });

            event.onComplete((e) -> {

                switch (e.getState()) {
                    case SUCCESS:
                        //事件执行成功
                        if (StrUtil.isNotBlank(klbChatConfig.getOutputField())) {
                            //配置项要求，把结果输出到上下文
                            context.setAttr(klbChatConfig.getOutputField(), e.getResult());
                        }
                        //向前端推送参考
                        if (e.isMatchSuccess() && klbChatConfig.isShowDocRefers()) {
                            context.sendReference(docs);
                        }
                    case CANCEL:
                        if (llmConfig.isPersistDb()) {//成功或者取消 需要持久化数据
                            //把答案给存储
                            context.getResponseMsg().appendContent(e.getAiChatEvent().getResult() != null ?e.getAiChatEvent().getResult().getContent() : null);
                            if (e.isMatchSuccess()) {
                                context.getResponseMsg().setDocs(JSONUtil.toJson(docs.stream().map(AiDoc::getId).collect(Collectors.toList())));
                            }
                        }
                        break;
                    case FAIL:

                }


            });
            return event;
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("ChatNodeHandler process 异常,异常原因", e);
            context.sendErrorMsg("网络异常,请稍后重试....");
        }
        return null;
    }

    private List<String> generateBackgroundText(List<AiDoc> docs) {
        if (docs == null || docs.size() == 0) {
            return Collections.emptyList();
        }
        //将结果保存到 输出字段中
        List<String> list = new ArrayList<>();
        for (AiDoc aiDoc : docs) {
            if (FileType.QA.getCode().equals(aiDoc.getDocType())) {
                list.add(aiDoc.getContent() + "\n" + aiDoc.getAnswer());
            } else {
                list.add(aiDoc.getContent());
            }

        }
        return list;
    }

}
